package com.iot.gateway.filter;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.iot.common.core.constant.RedisConstants;
import com.iot.common.core.constant.SecurityConstants;
import com.iot.common.core.exception.AuthorizationException;
import com.iot.common.core.util.Md5Util;
import com.iot.common.core.util.R;
import com.iot.common.security.domain.SimpleUser;
import com.iot.gateway.properties.GatewayExclusionUrlProperties;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.SignedJWT;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

/**
 * token过滤器
 * @author love
 * @version
 * @since JDK 1.8
 */
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {

	private StringRedisTemplate redisTemplate;

	private List<String> exclusionUrls;

	private AntPathMatcher antPathMatcher = new AntPathMatcher();

	public AuthGlobalFilter(StringRedisTemplate redisTemplate,
			GatewayExclusionUrlProperties gatewayExclusionUrlProperties) {
		this.exclusionUrls = gatewayExclusionUrlProperties != null ? gatewayExclusionUrlProperties.getUrl() : null;
		this.redisTemplate = redisTemplate;
	}

	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		ServerHttpRequest request = exchange.getRequest();
		ServerHttpResponse response = exchange.getResponse();

		try {
			String path = request.getURI().getPath();
			log.info("request path:{}", path);

			// 判断是否是过滤的路径， 是的话就放行
			if (isExclusionUrl(path)) {
				return chain.filter(exchange);
			}

			String authorization = request.getHeaders().getFirst(SecurityConstants.TOKEN_HEADER);

			log.debug("Authorization:{}", authorization);
			if (ObjectUtils.isEmpty(authorization) /* || !authorization.startsWith(SecurityConstants.TOKEN_PREFIX) */) {
				log.error("网关校验Authorization异常:{}", "访问资源受限！");
				return getVoidMono(response, HttpServletResponse.SC_FORBIDDEN, "访问资源受限！");
			}

			String token = authorization.replace(SecurityConstants.TOKEN_PREFIX, "");
			SignedJWT jwt = SignedJWT.parse(token);
			JWSVerifier verifier = new MACVerifier(SecurityConstants.TOKEN_SECRET);
			Object account = jwt.getJWTClaimsSet().getClaim("payload");
			if (!jwt.verify(verifier) || account == null) {
				throw new AuthorizationException(SecurityConstants.TOKEN_INVALID);
			}

			// 校验是否超时
			Date expirationTime = jwt.getJWTClaimsSet().getExpirationTime();
			if (new Date().after(expirationTime)) {
				throw new AuthorizationException(SecurityConstants.TOKEN_EXPIRE);
			}

			SimpleUser user = JSONObject.parseObject(account.toString(), SimpleUser.class);

			String keyPrefix = RedisConstants.TOKEN_KEY_PREFIX + user.getId() + ":";
			String keySuffix = Md5Util.getMD5(token.getBytes());
			String key = keyPrefix + keySuffix;

			String redisToken = redisTemplate.opsForValue().get(key);
			if (ObjectUtils.isEmpty(redisToken)) {
				throw new AuthorizationException(SecurityConstants.TOKEN_CANCEL);
			}

			if (!redisToken.equals(token)) {
				throw new AuthorizationException(SecurityConstants.TOKEN_INVALID);
			}

			String redisAuth = redisTemplate.opsForValue().get(key + RedisConstants.AUTH_KEY);
			log.info("redisPermission:{}", redisAuth);

			// 判断请求的URL是否有权限
//			boolean permission = hasPermission(path);
//			if (!permission) {
//				return getVoidMono(response, AuthEnum.AUTH_NO_ACCESS.getKey(), AuthEnum.AUTH_NO_ACCESS.getValue());
//			}
			return chain.filter(exchange);
		} catch (Exception e) {
			log.error("网关校验Authorization异常:{}", e.getMessage());
			return getVoidMono(response, 500, e.getMessage());
		}
	}

	private Mono<Void> getVoidMono(ServerHttpResponse response, int i, String msg) {
		response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
		response.setStatusCode(HttpStatus.OK);
		byte[] bits = JSON.toJSONString(R.failed(i, msg)).getBytes();
		DataBuffer buffer = response.bufferFactory().wrap(bits);
		return response.writeWith(Mono.just(buffer));
	}

	/**
	 * 白名单
	 * 
	 * @param path
	 * @return
	 */
	private boolean isExclusionUrl(String path) {
		if (exclusionUrls == null || exclusionUrls.size() == 0) {
			return false;
		}
		return exclusionUrls.stream().anyMatch(action -> antPathMatcher.match(action, path));
	}
	

	/**
	 * 判断接口是否有xxx:xxx权限
	 * 
	 * @param permission 权限
	 * @return {boolean}
	 */
	public boolean hasPermission(String permission) {
		if (StrUtil.isBlank(permission)) {
			return false;
		}
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		if (authentication == null) {
			return false;
		}
		Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
		return authorities.stream().map(GrantedAuthority::getAuthority).filter(StringUtils::hasText)
				.anyMatch(x -> PatternMatchUtils.simpleMatch(permission, x));
	}

}
